home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / dns / resolver.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  19.8 KB  |  704 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. '''DNS stub resolver.
  5.  
  6. @var default_resolver: The default resolver object
  7. @type default_resolver: dns.resolver.Resolver object'''
  8. import socket
  9. import sys
  10. import time
  11. import dns.exception as dns
  12. import dns.message as dns
  13. import dns.name as dns
  14. import dns.query as dns
  15. import dns.rcode as dns
  16. import dns.rdataclass as dns
  17. import dns.rdatatype as dns
  18. if sys.platform == 'win32':
  19.     import _winreg
  20.  
  21.  
  22. class NXDOMAIN(dns.exception.DNSException):
  23.     '''The query name does not exist.'''
  24.     pass
  25.  
  26. Timeout = dns.exception.Timeout
  27.  
  28. class NoAnswer(dns.exception.DNSException):
  29.     '''The response did not contain an answer to the question.'''
  30.     pass
  31.  
  32.  
  33. class NoNameservers(dns.exception.DNSException):
  34.     '''No non-broken nameservers are available to answer the query.'''
  35.     pass
  36.  
  37.  
  38. class NotAbsolute(dns.exception.DNSException):
  39.     '''Raised if an absolute domain name is required but a relative name
  40.     was provided.'''
  41.     pass
  42.  
  43.  
  44. class NoRootSOA(dns.exception.DNSException):
  45.     '''Raised if for some reason there is no SOA at the root name.
  46.     This should never happen!'''
  47.     pass
  48.  
  49.  
  50. class Answer(object):
  51.     '''DNS stub resolver answer
  52.  
  53.     Instances of this class bundle up the result of a successful DNS
  54.     resolution.
  55.  
  56.     For convenience, the answer is iterable.  "for a in answer" is
  57.     equivalent to "for a in answer.rrset".
  58.  
  59.     Note that CNAMEs or DNAMEs in the response may mean that answer
  60.     node\'s name might not be the query name.
  61.  
  62.     @ivar qname: The query name
  63.     @type qname: dns.name.Name object
  64.     @ivar rdtype: The query type
  65.     @type rdtype: int
  66.     @ivar rdclass: The query class
  67.     @type rdclass: int
  68.     @ivar response: The response message
  69.     @type response: dns.message.Message object
  70.     @ivar rrset: The answer
  71.     @type rrset: dns.rrset.RRset object
  72.     @ivar expiration: The time when the answer expires
  73.     @type expiration: float (seconds since the epoch)
  74.     '''
  75.     
  76.     def __init__(self, qname, rdtype, rdclass, response):
  77.         self.qname = qname
  78.         self.rdtype = rdtype
  79.         self.rdclass = rdclass
  80.         self.response = response
  81.         min_ttl = -1
  82.         rrset = None
  83.         for count in xrange(0, 15):
  84.             
  85.             try:
  86.                 rrset = response.find_rrset(response.answer, qname, rdclass, rdtype)
  87.                 if min_ttl == -1 or rrset.ttl < min_ttl:
  88.                     min_ttl = rrset.ttl
  89.                 
  90.             continue
  91.             except KeyError:
  92.                 if rdtype != dns.rdatatype.CNAME:
  93.                     
  94.                     try:
  95.                         crrset = response.find_rrset(response.answer, qname, rdclass, dns.rdatatype.CNAME)
  96.                         if min_ttl == -1 or crrset.ttl < min_ttl:
  97.                             min_ttl = crrset.ttl
  98.                         
  99.                         for rd in crrset:
  100.                             qname = rd.target
  101.                         
  102.                     except KeyError:
  103.                         raise NoAnswer
  104.                     except:
  105.                         None<EXCEPTION MATCH>KeyError
  106.                     
  107.  
  108.                 None<EXCEPTION MATCH>KeyError
  109.                 raise NoAnswer
  110.                 continue
  111.             
  112.  
  113.         
  114.         if rrset is None:
  115.             raise NoAnswer
  116.         
  117.         self.rrset = rrset
  118.         self.expiration = time.time() + min_ttl
  119.  
  120.     
  121.     def __getattr__(self, attr):
  122.         if attr == 'name':
  123.             return self.rrset.name
  124.         elif attr == 'ttl':
  125.             return self.rrset.ttl
  126.         elif attr == 'covers':
  127.             return self.rrset.covers
  128.         elif attr == 'rdclass':
  129.             return self.rrset.rdclass
  130.         elif attr == 'rdtype':
  131.             return self.rrset.rdtype
  132.         else:
  133.             raise AttributeError, attr
  134.  
  135.     
  136.     def __len__(self):
  137.         return len(self.rrset)
  138.  
  139.     
  140.     def __iter__(self):
  141.         return iter(self.rrset)
  142.  
  143.  
  144.  
  145. class Cache(object):
  146.     '''Simple DNS answer cache.
  147.  
  148.     @ivar data: A dictionary of cached data
  149.     @type data: dict
  150.     @ivar cleaning_interval: The number of seconds between cleanings.  The
  151.     default is 300 (5 minutes).
  152.     @type cleaning_interval: float
  153.     @ivar next_cleaning: The time the cache should next be cleaned (in seconds
  154.     since the epoch.)
  155.     @type next_cleaning: float
  156.     '''
  157.     
  158.     def __init__(self, cleaning_interval = 300.0):
  159.         '''Initialize a DNS cache.
  160.  
  161.         @param cleaning_interval: the number of seconds between periodic
  162.         cleanings.  The default is 300.0
  163.         @type cleaning_interval: float.
  164.         '''
  165.         self.data = { }
  166.         self.cleaning_interval = cleaning_interval
  167.         self.next_cleaning = time.time() + self.cleaning_interval
  168.  
  169.     
  170.     def maybe_clean(self):
  171.         """Clean the cache if it's time to do so."""
  172.         now = time.time()
  173.         if self.next_cleaning <= now:
  174.             keys_to_delete = []
  175.             for k, v in self.data.iteritems():
  176.                 if v.expiration <= now:
  177.                     keys_to_delete.append(k)
  178.                     continue
  179.             
  180.             for k in keys_to_delete:
  181.                 del self.data[k]
  182.             
  183.             now = time.time()
  184.             self.next_cleaning = now + self.cleaning_interval
  185.         
  186.  
  187.     
  188.     def get(self, key):
  189.         '''Get the answer associated with I{key}.  Returns None if
  190.         no answer is cached for the key.
  191.         @param key: the key
  192.         @type key: (dns.name.Name, int, int) tuple whose values are the
  193.         query name, rdtype, and rdclass.
  194.         @rtype: dns.resolver.Answer object or None
  195.         '''
  196.         self.maybe_clean()
  197.         v = self.data.get(key)
  198.         if v is None or v.expiration <= time.time():
  199.             return None
  200.         
  201.         return v
  202.  
  203.     
  204.     def put(self, key, value):
  205.         '''Associate key and value in the cache.
  206.         @param key: the key
  207.         @type key: (dns.name.Name, int, int) tuple whose values are the
  208.         query name, rdtype, and rdclass.
  209.         @param value: The answer being cached
  210.         @type value: dns.resolver.Answer object
  211.         '''
  212.         self.maybe_clean()
  213.         self.data[key] = value
  214.  
  215.     
  216.     def flush(self, key = None):
  217.         '''Flush the cache.
  218.  
  219.         If I{key} is specified, only that item is flushed.  Otherwise
  220.         the entire cache is flushed.
  221.  
  222.         @param key: the key to flush
  223.         @type key: (dns.name.Name, int, int) tuple or None
  224.         '''
  225.         if key is not None:
  226.             if self.data.has_key(key):
  227.                 del self.data[key]
  228.             
  229.         else:
  230.             self.data = { }
  231.             self.next_cleaning = time.time() + self.cleaning_interval
  232.  
  233.  
  234.  
  235. class Resolver(object):
  236.     '''DNS stub resolver
  237.  
  238.     @ivar domain: The domain of this host
  239.     @type domain: dns.name.Name object
  240.     @ivar nameservers: A list of nameservers to query.  Each nameserver is
  241.     a string which contains the IP address of a nameserver.
  242.     @type nameservers: list of strings
  243.     @ivar search: The search list.  If the query name is a relative name,
  244.     the resolver will construct an absolute query name by appending the search
  245.     names one by one to the query name.
  246.     @type search: list of dns.name.Name objects
  247.     @ivar port: The port to which to send queries.  The default is 53.
  248.     @type port: int
  249.     @ivar timeout: The number of seconds to wait for a response from a
  250.     server, before timing out.
  251.     @type timeout: float
  252.     @ivar lifetime: The total number of seconds to spend trying to get an
  253.     answer to the question.  If the lifetime expires, a Timeout exception
  254.     will occur.
  255.     @type lifetime: float
  256.     @ivar keyring: The TSIG keyring to use.  The default is None.
  257.     @type keyring: dict
  258.     @ivar keyname: The TSIG keyname to use.  The default is None.
  259.     @type keyname: dns.name.Name object
  260.     @ivar edns: The EDNS level to use.  The default is -1, no Edns.
  261.     @type edns: int
  262.     @ivar ednsflags: The EDNS flags
  263.     @type ednsflags: int
  264.     @ivar payload: The EDNS payload size.  The default is 0.
  265.     @type payload: int
  266.     @ivar cache: The cache to use.  The default is None.
  267.     @type cache: dns.resolver.Cache object
  268.     '''
  269.     
  270.     def __init__(self, filename = '/etc/resolv.conf', configure = True):
  271.         '''Initialize a resolver instance.
  272.  
  273.         @param filename: The filename of a configuration file in
  274.         standard /etc/resolv.conf format.  This parameter is meaningful
  275.         only when I{configure} is true and the platform is POSIX.
  276.         @type filename: string or file object
  277.         @param configure: If True (the default), the resolver instance
  278.         is configured in the normal fashion for the operating system
  279.         the resolver is running on.  (I.e. a /etc/resolv.conf file on
  280.         POSIX systems and from the registry on Windows systems.)
  281.         @type configure: bool'''
  282.         self.reset()
  283.         if configure:
  284.             if sys.platform == 'win32':
  285.                 self.read_registry()
  286.             elif filename:
  287.                 self.read_resolv_conf(filename)
  288.             
  289.         
  290.  
  291.     
  292.     def reset(self):
  293.         '''Reset all resolver configuration to the defaults.'''
  294.         self.domain = dns.name.Name(dns.name.from_text(socket.gethostname())[1:])
  295.         if len(self.domain) == 0:
  296.             self.domain = dns.name.root
  297.         
  298.         self.nameservers = []
  299.         self.search = []
  300.         self.port = 53
  301.         self.timeout = 2.0
  302.         self.lifetime = 30.0
  303.         self.keyring = None
  304.         self.keyname = None
  305.         self.edns = -1
  306.         self.ednsflags = 0
  307.         self.payload = 0
  308.         self.cache = None
  309.  
  310.     
  311.     def read_resolv_conf(self, f):
  312.         '''Process f as a file in the /etc/resolv.conf format.  If f is
  313.         a string, it is used as the name of the file to open; otherwise it
  314.         is treated as the file itself.'''
  315.         if isinstance(f, str) or isinstance(f, unicode):
  316.             f = open(f, 'r')
  317.             want_close = True
  318.         else:
  319.             want_close = False
  320.         
  321.         try:
  322.             for l in f:
  323.                 if len(l) == 0 and l[0] == '#' or l[0] == ';':
  324.                     continue
  325.                 
  326.                 tokens = l.split()
  327.                 if len(tokens) == 0:
  328.                     continue
  329.                 
  330.                 if tokens[0] == 'nameserver':
  331.                     self.nameservers.append(tokens[1])
  332.                     continue
  333.                 if tokens[0] == 'domain':
  334.                     self.domain = dns.name.from_text(tokens[1])
  335.                     continue
  336.                 if tokens[0] == 'search':
  337.                     for suffix in tokens[1:]:
  338.                         self.search.append(dns.name.from_text(suffix))
  339.                     
  340.         finally:
  341.             if want_close:
  342.                 f.close()
  343.             
  344.  
  345.         if len(self.nameservers) == 0:
  346.             self.nameservers.append('127.0.0.1')
  347.         
  348.  
  349.     
  350.     def _config_win32_nameservers(self, nameservers, split_char = ','):
  351.         '''Configure a NameServer registry entry.'''
  352.         ns_list = str(nameservers).split(split_char)
  353.         for ns in ns_list:
  354.             if ns not in self.nameservers:
  355.                 self.nameservers.append(ns)
  356.                 continue
  357.         
  358.  
  359.     
  360.     def _config_win32_domain(self, domain):
  361.         '''Configure a Domain registry entry.'''
  362.         self.domain = dns.name.from_text(str(domain))
  363.  
  364.     
  365.     def _config_win32_search(self, search):
  366.         '''Configure a Search registry entry.'''
  367.         search_list = str(search).split(',')
  368.         for s in search_list:
  369.             if s not in self.search:
  370.                 self.search.append(dns.name.from_text(s))
  371.                 continue
  372.         
  373.  
  374.     
  375.     def _config_win32_fromkey(self, key):
  376.         '''Extract DNS info from a registry key.'''
  377.         
  378.         try:
  379.             (servers, rtype) = _winreg.QueryValueEx(key, 'NameServer')
  380.         except WindowsError:
  381.             servers = None
  382.  
  383.         if servers:
  384.             self._config_win32_nameservers(servers)
  385.             
  386.             try:
  387.                 (dom, rtype) = _winreg.QueryValueEx(key, 'Domain')
  388.                 if dom:
  389.                     self._config_win32_domain(servers)
  390.             except WindowsError:
  391.                 pass
  392.             except:
  393.                 None<EXCEPTION MATCH>WindowsError
  394.             
  395.  
  396.         None<EXCEPTION MATCH>WindowsError
  397.         
  398.         try:
  399.             (servers, rtype) = _winreg.QueryValueEx(key, 'DhcpNameServer')
  400.         except WindowsError:
  401.             servers = None
  402.  
  403.         if servers:
  404.             self._config_win32_nameservers(servers, ' ')
  405.             
  406.             try:
  407.                 (dom, rtype) = _winreg.QueryValueEx(key, 'DhcpDomain')
  408.                 if dom:
  409.                     self._config_win32_domain(servers)
  410.             except WindowsError:
  411.                 pass
  412.             except:
  413.                 None<EXCEPTION MATCH>WindowsError
  414.             
  415.  
  416.         None<EXCEPTION MATCH>WindowsError
  417.         
  418.         try:
  419.             (search, rtype) = _winreg.QueryValueEx(key, 'SearchList')
  420.         except WindowsError:
  421.             search = None
  422.  
  423.         if search:
  424.             self._config_win32_search(servers)
  425.         
  426.  
  427.     
  428.     def read_registry(self):
  429.         '''Extract resolver configuration from the Windows registry.'''
  430.         lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
  431.         want_scan = False
  432.         
  433.         try:
  434.             tcp_params = _winreg.OpenKey(lm, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters')
  435.             want_scan = True
  436.         except EnvironmentError:
  437.             tcp_params = _winreg.OpenKey(lm, 'SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP')
  438.         
  439.  
  440.         
  441.         try:
  442.             self._config_win32_fromkey(tcp_params)
  443.         finally:
  444.             tcp_params.Close()
  445.  
  446.         if want_scan:
  447.             interfaces = _winreg.OpenKey(lm, 'SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces')
  448.             
  449.             try:
  450.                 i = 0
  451.                 while True:
  452.                     
  453.                     try:
  454.                         guid = _winreg.EnumKey(interfaces, i)
  455.                         i += 1
  456.                         key = _winreg.OpenKey(interfaces, guid)
  457.                         
  458.                         try:
  459.                             (nte, ttype) = _winreg.QueryValueEx(key, 'NTEContextList')
  460.                         except WindowsError:
  461.                             nte = None
  462.                         
  463.  
  464.                         if nte:
  465.                             self._config_win32_fromkey(key)
  466.  
  467.                 continue
  468.                 except EnvironmentError:
  469.                     break
  470.                     continue
  471.                 
  472.                 None<EXCEPTION MATCH>EnvironmentError
  473.             finally:
  474.                 interfaces.Close()
  475.  
  476.         lm.Close()
  477.  
  478.     
  479.     def _compute_timeout(self, start):
  480.         now = time.time()
  481.         if now < start:
  482.             if start - now > 1:
  483.                 raise Timeout
  484.             else:
  485.                 now = start
  486.         
  487.         duration = now - start
  488.         if duration >= self.lifetime:
  489.             raise Timeout
  490.         
  491.         return min(self.lifetime - duration, self.timeout)
  492.  
  493.     
  494.     def query(self, qname, rdtype = dns.rdatatype.A, rdclass = dns.rdataclass.IN, tcp = False):
  495.         """Query nameservers to find the answer to the question.
  496.  
  497.         The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects
  498.         of the appropriate type, or strings that can be converted into objects
  499.         of the appropriate type.  E.g. For I{rdtype} the integer 2 and the
  500.         the string 'NS' both mean to query for records with DNS rdata type NS.
  501.         
  502.         @param qname: the query name
  503.         @type qname: dns.name.Name object or string
  504.         @param rdtype: the query type
  505.         @type rdtype: int or string
  506.         @param rdclass: the query class
  507.         @type rdclass: int or string
  508.         @param tcp: use TCP to make the query (default is False).
  509.         @type tcp: bool
  510.         @rtype: dns.resolver.Answer instance
  511.         @raises Timeout: no answers could be found in the specified lifetime
  512.         @raises NXDOMAIN: the query name does not exist
  513.         @raises NoAnswer: the response did not contain an answer
  514.         @raises NoNameservers: no non-broken nameservers are available to
  515.         answer the question."""
  516.         if isinstance(qname, str):
  517.             qname = dns.name.from_text(qname, None)
  518.         
  519.         if isinstance(rdtype, str):
  520.             rdtype = dns.rdatatype.from_text(rdtype)
  521.         
  522.         if isinstance(rdclass, str):
  523.             rdclass = dns.rdataclass.from_text(rdclass)
  524.         
  525.         qnames_to_try = []
  526.         if qname.is_absolute():
  527.             qnames_to_try.append(qname)
  528.         elif len(qname) > 1:
  529.             qnames_to_try.append(qname.concatenate(dns.name.root))
  530.         
  531.         if self.search:
  532.             for suffix in self.search:
  533.                 qnames_to_try.append(qname.concatenate(suffix))
  534.             
  535.         else:
  536.             qnames_to_try.append(qname.concatenate(self.domain))
  537.         all_nxdomain = True
  538.         start = time.time()
  539.         for qname in qnames_to_try:
  540.             if self.cache:
  541.                 answer = self.cache.get((qname, rdtype, rdclass))
  542.                 if answer:
  543.                     return answer
  544.                 
  545.             
  546.             request = dns.message.make_query(qname, rdtype, rdclass)
  547.             if self.keyname is not None:
  548.                 request.use_tsig(self.keyring, self.keyname)
  549.             
  550.             request.use_edns(self.edns, self.ednsflags, self.payload)
  551.             response = None
  552.             nameservers = self.nameservers[:]
  553.             backoff = 0.10000000000000001
  554.             while response is None:
  555.                 if len(nameservers) == 0:
  556.                     raise NoNameservers
  557.                 
  558.                 for nameserver in nameservers:
  559.                     timeout = self._compute_timeout(start)
  560.                     
  561.                     try:
  562.                         if tcp:
  563.                             response = dns.query.tcp(request, nameserver, timeout, self.port)
  564.                         else:
  565.                             response = dns.query.udp(request, nameserver, timeout, self.port)
  566.                     except (socket.error, dns.exception.Timeout):
  567.                         response = None
  568.                         continue
  569.                     except dns.query.UnexpectedSource:
  570.                         response = None
  571.                         continue
  572.                     except dns.exception.FormError:
  573.                         nameservers.remove(nameserver)
  574.                         response = None
  575.                         continue
  576.  
  577.                     rcode = response.rcode()
  578.                     if rcode == dns.rcode.NOERROR or rcode == dns.rcode.NXDOMAIN:
  579.                         break
  580.                     
  581.                     if rcode != dns.rcode.SERVFAIL:
  582.                         nameservers.remove(nameserver)
  583.                     
  584.                     response = None
  585.                 
  586.                 if len(nameservers) > 0:
  587.                     timeout = self._compute_timeout(start)
  588.                     sleep_time = min(timeout, backoff)
  589.                     backoff *= 2
  590.                     time.sleep(sleep_time)
  591.                     continue
  592.             if response.rcode() == dns.rcode.NXDOMAIN:
  593.                 continue
  594.             
  595.             all_nxdomain = False
  596.         
  597.         if all_nxdomain:
  598.             raise NXDOMAIN
  599.         
  600.         answer = Answer(qname, rdtype, rdclass, response)
  601.         if self.cache:
  602.             self.cache.put((qname, rdtype, rdclass), answer)
  603.         
  604.         return answer
  605.  
  606.     
  607.     def use_tsig(self, keyring, keyname = None):
  608.         '''Add a TSIG signature to the query.
  609.  
  610.         @param keyring: The TSIG keyring to use; defaults to None.
  611.         @type keyring: dict
  612.         @param keyname: The name of the TSIG key to use; defaults to None.
  613.         The key must be defined in the keyring.  If a keyring is specified
  614.         but a keyname is not, then the key used will be the first key in the
  615.         keyring.  Note that the order of keys in a dictionary is not defined,
  616.         so applications should supply a keyname when a keyring is used, unless
  617.         they know the keyring contains only one key.'''
  618.         self.keyring = keyring
  619.         if keyname is None:
  620.             self.keyname = self.keyring.keys()[0]
  621.         else:
  622.             self.keyname = keyname
  623.  
  624.     
  625.     def use_edns(self, edns, ednsflags, payload):
  626.         '''Configure Edns.
  627.  
  628.         @param edns: The EDNS level to use.  The default is -1, no Edns.
  629.         @type edns: int
  630.         @param ednsflags: The EDNS flags
  631.         @type ednsflags: int
  632.         @param payload: The EDNS payload size.  The default is 0.
  633.         @type payload: int'''
  634.         if edns is None:
  635.             edns = -1
  636.         
  637.         self.edns = edns
  638.         self.ednsflags = ednsflags
  639.         self.payload = payload
  640.  
  641.  
  642. default_resolver = None
  643.  
  644. def get_default_resolver():
  645.     '''Get the default resolver, initializing it if necessary.'''
  646.     global default_resolver
  647.     if default_resolver is None:
  648.         default_resolver = Resolver()
  649.     
  650.     return default_resolver
  651.  
  652.  
  653. def query(qname, rdtype = dns.rdatatype.A, rdclass = dns.rdataclass.IN, tcp = False):
  654.     '''Query nameservers to find the answer to the question.
  655.  
  656.     This is a convenience function that uses the default resolver
  657.     object to make the query.
  658.     @see: L{dns.resolver.Resolver.query} for more information on the
  659.     parameters.'''
  660.     return get_default_resolver().query(qname, rdtype, rdclass, tcp)
  661.  
  662.  
  663. def zone_for_name(name, rdclass = dns.rdataclass.IN, tcp = False, resolver = None):
  664.     '''Find the name of the zone which contains the specified name.
  665.  
  666.     @param name: the query name
  667.     @type name: absolute dns.name.Name object or string
  668.     @param rdclass: The query class
  669.     @type rdclass: int
  670.     @param tcp: use TCP to make the query (default is False).
  671.     @type tcp: bool
  672.     @param resolver: the resolver to use
  673.     @type resolver: dns.resolver.Resolver object or None
  674.     @rtype: dns.name.Name'''
  675.     if isinstance(name, str):
  676.         name = dns.name.from_text(name, dns.name.root)
  677.     
  678.     if resolver is None:
  679.         resolver = get_default_resolver()
  680.     
  681.     if not name.is_absolute():
  682.         raise NotAbsolute, name
  683.     
  684.     while None:
  685.         
  686.         try:
  687.             answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp)
  688.             return name
  689.         continue
  690.         except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
  691.             
  692.             try:
  693.                 name = name.parent()
  694.             except dns.name.NoParent:
  695.                 raise NoRootSOA
  696.             except:
  697.                 None<EXCEPTION MATCH>dns.name.NoParent
  698.             
  699.  
  700.             None<EXCEPTION MATCH>dns.name.NoParent
  701.         
  702.  
  703.  
  704.